This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / routes / [handle] / [repo] / +layout.ts
4.3 kB 114 lines
1import { error, redirect } from "@sveltejs/kit"; 2import { createBobbinClient } from "$lib/api/client"; 3import { count } from "$lib/api/count"; 4import { gitTarget, resolveDefaultBranch } from "$lib/api/gitclient"; 5import { getStarRkey } from "$lib/api/graph"; 6import { IdentityCache, resolveMiniDoc } from "$lib/api/identity"; 7import { parallel, toHttpError } from "$lib/api/load"; 8import { getRepo } from "$lib/api/records"; 9import { repoNameOf, resolveRepoByName } from "$lib/api/repo"; 10import { didFromUri, rkeyFromUri } from "$lib/api/uri"; 11import type { BobbinContext } from "$lib/api/client"; 12import type { RepoCounts, RepoInfo, RepoSource } from "$lib/components/repo/types"; 13import type { LayoutLoad } from "./$types"; 14 15const FALLBACK_BRANCH = "main"; 16 17const resolveSource = async ( 18 ctx: BobbinContext, 19 uri: string | undefined 20): Promise<RepoSource | null> => { 21 if (!uri?.startsWith("at://")) return null; 22 try { 23 const view = await getRepo(ctx, uri); 24 const ownerDid = didFromUri(view.uri); 25 const owner = await new IdentityCache(ctx).resolve(ownerDid).catch(() => null); 26 return { ownerHandle: owner?.handle ?? ownerDid, name: repoNameOf(view) }; 27 } catch { 28 // a fork whose source is gone still renders, just without the attribution 29 return null; 30 } 31}; 32 33// the layout reset also cuts us off from `[handle]/+layout.ts`, so identity gets 34// resolved again here 35export const load: LayoutLoad = async (event) => { 36 const parent = await event.parent(); 37 const identifier = decodeURIComponent(event.params.handle); 38 const name = decodeURIComponent(event.params.repo); 39 40 // rejects bare words so unrelated paths 404 instead of resolving as actors 41 if (!identifier.startsWith("did:") && !identifier.includes(".")) { 42 error(404, "Not found"); 43 } 44 45 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); 46 const doc = await resolveMiniDoc(ctx, identifier).catch((cause) => 47 toHttpError(cause, "Could not resolve user") 48 ); 49 50 // redirects dids and stale handles to the canonical handle url 51 const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null; 52 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) { 53 redirect(307, `/${canonical}/${event.params.repo}${event.url.search}`); 54 } 55 56 const view = await resolveRepoByName(ctx, doc.did, name).catch((cause) => 57 toHttpError(cause, "Could not load repository") 58 ); 59 if (!view) error(404, `${doc.handle}/${name} does not exist`); 60 61 const record = view.value; 62 const repoDid = record.repoDid; 63 const viewerDid = parent.auth?.did; 64 const git = gitTarget(parent.publicConfig, { uri: view.uri, repoDid }, event.fetch); 65 66 const stats = await parallel({ 67 // only the git backend knows the default branch, and one that is down 68 // or still syncing shouldn't take out the whole layout 69 defaultBranch: resolveDefaultBranch(git), 70 stars: repoDid 71 ? count(ctx, "sh.tangled.feed.countStars", repoDid).catch(() => null) 72 : Promise.resolve(null), 73 // the tabs count what is still open, closed ones matter on their own pages 74 issues: repoDid 75 ? count(ctx, "sh.tangled.repo.countIssues", repoDid, { state: "open" }).catch(() => null) 76 : Promise.resolve(null), 77 pulls: repoDid 78 ? count(ctx, "sh.tangled.repo.countPulls", repoDid, { status: "open" }).catch(() => null) 79 : Promise.resolve(null), 80 forks: repoDid 81 ? count(ctx, "sh.tangled.repo.countForks", repoDid).catch(() => null) 82 : Promise.resolve(null), 83 viewerStarRkey: 84 viewerDid && repoDid 85 ? getStarRkey(ctx, viewerDid, repoDid).catch(() => null) 86 : Promise.resolve(null), 87 source: resolveSource(ctx, record.source) 88 }); 89 90 const repo: RepoInfo = { 91 uri: view.uri, 92 rkey: rkeyFromUri(view.uri), 93 name: repoNameOf(view), 94 ownerDid: doc.did, 95 ownerHandle: doc.handle, 96 repoDid, 97 knot: record.knot, 98 spindle: record.spindle, 99 description: record.description, 100 website: record.website, 101 topics: record.topics, 102 source: stats.source ?? undefined, 103 defaultBranch: stats.defaultBranch ?? FALLBACK_BRANCH 104 }; 105 106 const counts: RepoCounts = { 107 stars: stats.stars?.count ?? 0, 108 issues: stats.issues?.count ?? 0, 109 pulls: stats.pulls?.count ?? 0, 110 forks: stats.forks?.count ?? 0 111 }; 112 113 return { repo, counts, viewerStarRkey: stats.viewerStarRkey }; 114};